//
//  NowPlayingManager.swift
//  GhostWorld
//
//  Created by Kam on 2018/4/29.
//  Copyright © 2018 LKGood. All rights reserved.
//

import Foundation
import MediaPlayer

private let latestPlayingAlbumIDKey = "LatestPlayingAlbumIDKey"
private let latestPlayingAudioIDKey = "LatestPlayingAudioIDKey"
private let latestPlayingAudioIDListKey = "LatestPlayingAudioIDListKey"

class NowPlayingManager {
  
  enum AutoSleepType {
    case disabled, afterCurrentAudio, afterSeconds(TimeInterval)
    
    static func ==(lhs: AutoSleepType, rhs: AutoSleepType) -> Bool {
      switch (lhs, rhs) {
      case (.disabled, .disabled),
           (.afterCurrentAudio, .afterCurrentAudio):
        return true
      case let (.afterSeconds(a), .afterSeconds(b)):
        return a == b
      default:
        return false
      }
    }
  }
  
  // MARK: - Singleton
  
  static let shared = NowPlayingManager()
  private init() { }
  
  // MARK: - Private vars
  
  private var autoSleepTimer: Timer?
  private var autoSleepSeconds: TimeInterval = 0
  
  private var audioList: [Audio]?
  private var audioListName: String?
  private var audioListType: RestoreAudioListType = .unknown
  private var audioListRefID: String?
  
  private var playTimer: Timer?
  private var playTimerSeconds = 0
  private var playTimerAudioID: Int = 0
  private var playTimerAudioName: String = ""
  
  // MARK: - Private methods
  
  private func displayNowPlayingInfo() {
    if let album = self.album, let audio = self.audio {
      var info: [String : Any] = [
        MPMediaItemPropertyAlbumTitle: album.name ?? "未知节目",
        MPMediaItemPropertyTitle: audio.name ?? "未知声音"
      ]
      info[MPMediaItemPropertyMediaType] = MPMediaType.anyAudio.rawValue
      
      if let url = album.cover.getURL(size: .original) {
        do {
          let imageData = try Data(contentsOf: url)
          if let coverImage = UIImage(data: imageData) {
            info[MPMediaItemPropertyArtwork] =
              MPMediaItemArtwork (
                boundsSize: coverImage.size,
                requestHandler: { (size) -> UIImage in
                  return coverImage
              })
          } else {
            loggingPrint("Loading cover image for InfoCenter failed, invalid image data")
          }
        } catch {
          loggingPrint("Loading cover image for InfoCenter failed, cannot load image from url")
        }
        
      }
      MPNowPlayingInfoCenter.default().nowPlayingInfo = info
    }
  }
  
  private func saveNowPlayingInfoForRestorationAfterRelaunchApp() {
    if let album = self.album,
      let audio = self.audio,
      let audios = self.audioList {
      
      do {
        try RestoreAudioList.update (
          nowPlayingAlbumID: Int(album.id),
          nowPlayingAudioID: Int(audio.id),
          audios: audios,
          refID: audioListRefID,
          name: audioListName,
          type: audioListType)
      } catch let error {
        loggingPrint(error)
      }
    }
  }
  
  private func addPlayHistory(album: Album, audio: Audio) {
    do {
      try PlayHistory.add(albumID: Int(album.id), audioID: Int(audio.id), relatedAudios: audioList)
    } catch let error {
      loggingPrint("Add play history failed. \(error)")
    }
  }
  
  // MARK: - Public vars
  
  var album: Album?
  var audio: Audio?
  
  var canPlayNextAuidoInList: Bool {
    get {
      if let list = audioList, let currentAudio = audio {
        for (index, audioInList) in list.enumerated() {
          if audioInList.id == currentAudio.id {
            if index + 1 < list.count {
              return true
            }
          }
        }
      }
      return false
    }
  }
  
  var canPlayPrevAuidoInList: Bool {
    get {
      if let list = audioList, let currentAudio = audio {
        for (index, audioInList) in list.enumerated() {
          if audioInList.id == currentAudio.id {
            if index - 1 >= 0 {
              return true
            }
          }
        }
      }
      return false
    }
  }
  
  var autoSleepLeftSeconds: TimeInterval = 0
  var autoSleepType: AutoSleepType = .disabled
  
  // MARK: - Public methods
  
  func prepare() {
    
    UIApplication.shared.beginReceivingRemoteControlEvents()
    
    // restore latest playing audio
    if let restoreAudioList = RestoreAudioList.get() {
      let latestPlayingAlbumID = restoreAudioList.nowPlayingAlbumID
      let latestPlayingAudioID = restoreAudioList.nowPlayingAudioID
      
      if let lastestPlayingAlbum = EntityManager.fetch(Album.self, fetchType: .id(Int(latestPlayingAlbumID)))?.first,
        let latestPlayingAudio = EntityManager.fetch(Audio.self, fetchType: .id(Int(latestPlayingAudioID)))?.first {
       
        album = lastestPlayingAlbum
        audio = latestPlayingAudio
        displayNowPlayingInfo()
        
        // restore playtime
        if let dict = UserDefaults.standard.dictionary(forKey: "SavedPlayTime"),
          let audioID = dict["AudioID"] as? Int,
          let audioName = dict["AudioName"] as? String,
          let seconds = dict["Seconds"] as? Int {
          
          playTimerAudioID = audioID
          playTimerAudioName = audioName
          playTimerSeconds = seconds
        } else {
          playTimerAudioID = Int(audio!.id)
          playTimerAudioName = audio!.name!
        }
        
        if let audios = restoreAudioList.getAudios() {
          audioList = audios
        }
        
        // restore play core
        if let audioURLString = audio?.url,
          let url = URL(string: audioURLString),
          let fileName = audio?.cacheFileName {
          
          PlayCore.shared.prepare(url: url, cacheFileName: fileName) { success in
            if success {
              let progress = self.audio!.getPlayProgress()
              DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                if progress > 0.999 {
                  PlayCore.shared.seek(toProgress: 0)
                } else {
                  PlayCore.shared.seek(toProgress: progress)
                }
              }
              loggingPrint("Loaded latest playing audio")
            } else {
              loggingPrint("Load latest playing audio failed!")
            }
          }
        }
      }
    }
  }
  
  func setNowPlayingElapsedPlaybackTime(duration: Double, seconds: Double, rate: Double) {
    let center = MPNowPlayingInfoCenter.default()
    if var info = center.nowPlayingInfo {
      info[MPNowPlayingInfoPropertyPlaybackRate] = rate
      info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = seconds
      info[MPMediaItemPropertyPlaybackDuration] = duration
      
      if let album = self.album, let audio = self.audio {
        info[MPMediaItemPropertyAlbumTitle] = album.name ?? "未知节目"
        info[MPMediaItemPropertyTitle] = audio.name ?? "未知声音"
      }
      center.nowPlayingInfo = info
    }
  }
  
  func update(audioList: [Audio]?, audioListName: String?, audioLsitType: RestoreAudioListType, audioListRefID: String?) {
    self.audioList = audioList
    self.audioListName = audioListName
    self.audioListType = audioLsitType
    self.audioListRefID = audioListRefID
  }
  
  func update(audioList: [Audio]?) {
    self.audioList = audioList
    saveNowPlayingInfoForRestorationAfterRelaunchApp()
  }
  
  func getAudioList() -> [Audio]? {
    return audioList
  }
  
  func getAudioListType() -> RestoreAudioListType {
    return audioListType
  }
  
  func getAudioListRefID() -> String? {
    return audioListRefID
  }
  
  func play(album: Album, audio: Audio) {
    if album.id != self.album?.id || audio.id != self.audio?.id {
      // new
      self.album = album
      self.audio = audio
      
      if let audioURLString = audio.url,
        let url = URL(string: audioURLString),
        let fileName = audio.cacheFileName {
        
        PlayCore.shared.prepare(url: url, cacheFileName: fileName) { success in
          if success {
            PlayCore.shared.startPlay()
            let progress = audio.getPlayProgress()
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
              if progress > 0.999 {
                PlayCore.shared.seek(toProgress: 0)
              } else {
                PlayCore.shared.seek(toProgress: progress)
              }
            }
            
            NotificationCenter.default.post(name: .changeNowPlaying, object: nil)
          } else {
            self.album = nil
            self.audio = nil
          }
        }
      } else {
        self.album = nil
        self.audio = nil
      }
      
      saveNowPlayingInfoForRestorationAfterRelaunchApp()
      
      startPlayTimer()
      playTimerAudioID = Int(audio.id)
      playTimerAudioName = audio.name!
    } else {
      // start play current audio
      if PlayCore.shared.isPlaying == false {
        PlayCore.shared.playPause()
      }
    }
    displayNowPlayingInfo()
    addPlayHistory(album: album, audio: audio)
    NotificationCenter.default.post(name: .nowPlayingMediaInfoChanged, object: nil)
  }
  
  func playNextAudioInList(byUser: Bool = true) {
    if byUser == false && self.autoSleepType == AutoSleepType.afterCurrentAudio {
      // auto sleep encountered
      PlayCore.shared.pauseForAutoSleep()
      autoSleepType = .disabled
      NotificationCenter.default.post(name: .autoSleepDidEncountered, object: nil)
    } else {
      // play next audio in list
      if let list = audioList, let currentAudio = audio {
        for (index, audioInList) in list.enumerated() {
          if audioInList.id == currentAudio.id {
            if index + 1 < list.count {
              let nextAudio = list[index + 1]
              let nextAlbum = nextAudio.album!
              play(album: nextAlbum, audio: nextAudio)
            }
          }
        }
      }
    }
  }
  
  func playPrevAudioInList() {
    if let list = audioList, let currentAudio = audio {
      for (index, audioInList) in list.enumerated() {
        if audioInList.id == currentAudio.id {
          if index - 1 >= 0 {
            let prevAudio = list[index - 1]
            let prevAlbum = prevAudio.album!
            play(album: prevAlbum, audio: prevAudio)
          }
        }
      }
    }
  }
  
  func clear() {
    NowPlayingManager.shared.album = nil
    NowPlayingManager.shared.audio = nil
    NowPlayingManager.shared.audioList = nil
    MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
    NotificationCenter.default.post(name: .changeNowPlaying, object: nil)
    
    autoSleepTimer?.invalidate()
    autoSleepSeconds = 0
    autoSleepLeftSeconds = 0
    autoSleepType = .disabled
    NotificationCenter.default.post(name: .autoSleepCountDidChange, object: nil)
  }
  
  private func startPlayTimer() {
    if playTimerSeconds > 0 {
      PlaytimeManager.shared.save(audioID: playTimerAudioID, seconds: playTimerSeconds)
    }
    playTimerSeconds = 0
    resumePlayTimer()
  }
  
  func pausePlayTimer() {
    playTimer?.invalidate()
    playTimer = nil
  }
  
  func resumePlayTimer() {
    pausePlayTimer()
    
    if PlayCore.shared.isPlaying {
      playTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
        self.playTimerSeconds += 1
        loggingPrint("[PLAYTIME] running. audio: \(self.playTimerAudioID) \(self.playTimerAudioName), seconds: \(self.playTimerSeconds)")
      })
      loggingPrint("[PLAYTIME] resumed.")
    }
  }
  
  func savePlaytimeBeforeAppTerminated() {
    if playTimerSeconds > 0 {
      let dict: [String : Any] = [
        "AudioID": playTimerAudioID,
        "AudioName": playTimerAudioName,
        "Seconds": playTimerSeconds
        ]
      UserDefaults.standard.set(dict, forKey: "SavedPlayTime")
      UserDefaults.standard.synchronize()
    }
  }
}

// MARK: - Sleep time

extension NowPlayingManager {
  func set(autoSleepType: AutoSleepType) {
    self.autoSleepType = autoSleepType
    autoSleepTimer?.invalidate()
    autoSleepSeconds = 0
    
    switch autoSleepType {
    case .afterSeconds(let seconds):
      autoSleepSeconds = seconds
      autoSleepLeftSeconds = seconds
      autoSleepTimer = Timer.scheduledTimer (
        withTimeInterval: 1,
        repeats: true,
        block: { timer in
          if self.autoSleepLeftSeconds <= 0 {
            // auto sleep encountered
            PlayCore.shared.pauseForAutoSleep()
            self.autoSleepType = .disabled
            timer.invalidate()
            NotificationCenter.default.post(name: .autoSleepDidEncountered, object: nil)
          } else {
            self.autoSleepLeftSeconds -= 1
            NotificationCenter.default.post(name: .autoSleepCountDidChange, object: nil)
          }
      })
    default:
      break
    }
  }
}
